package com.wonderful.compose.blur

import androidx.compose.foundation.gestures.awaitEachGesture
import androidx.compose.foundation.gestures.awaitFirstDown
import androidx.compose.foundation.gestures.awaitTouchSlopOrCancellation
import androidx.compose.foundation.gestures.drag
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.input.pointer.AwaitPointerEventScope
import androidx.compose.ui.input.pointer.PointerInputChange
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.input.pointer.positionChange

/**
 * Desc: 监听手势工具类
 * @Author: wonderful
 * @Time: 2024/2/1 20:24
 */

/**
 * @author SmartToolFactory
 *
 * from: https://github.com/SmartToolFactory/Compose-Drawing-App
 */
suspend fun AwaitPointerEventScope.awaitDragMotionEvent(onTouchEvent: (MotionEvent, PointerInputChange) -> Unit) {
    // wait for at least one pointer to press down, and set first contact position
    val down: PointerInputChange = awaitFirstDown()
    onTouchEvent(MotionEvent.DOWN, down)

    var pointer = down

    // 🔥 waits for drag threshold to be passed by pointer or it returns null if up event is triggered
    val change: PointerInputChange? = awaitTouchSlopOrCancellation(down.id) { change, overSlop ->
        // 🔥🔥 If consumePositionChange() is not consumed drag does not function properly.
        // Consuming position change causes change.positionChanged() to return false.
        if (change.positionChange() != Offset.Zero) change.consume()
    }

    if (change != null) {
        // 🔥 Calls  awaitDragOrCancellation(pointer) in a while loop
        drag(change.id) { pointerInputChange ->
            pointer = pointerInputChange
            onTouchEvent(MotionEvent.MOVE, pointer)
        }

        // All of the pointers are up
        onTouchEvent(MotionEvent.UP, pointer)
    } else {
        // drag threshold is not passed and last pointer is up
        onTouchEvent(MotionEvent.UP, pointer)
    }
}

fun Modifier.dragMotionEvent(onTouchEvent: (MotionEvent, PointerInputChange) -> Unit) = this.then(
    Modifier.pointerInput(Unit) {
        awaitEachGesture {
            awaitDragMotionEvent(onTouchEvent)
        }
    }
)

suspend fun AwaitPointerEventScope.awaitDragMotionEvent(
    onDragStart: (PointerInputChange) -> Unit = {},
    onDrag: (PointerInputChange) -> Unit = {},
    onDragEnd: (PointerInputChange) -> Unit = {}
) {
    // Wait for at least one pointer to press down, and set first contact position
    val down: PointerInputChange = awaitFirstDown()
    onDragStart(down)

    var pointer = down

    // 🔥 Waits for drag threshold to be passed by pointer or it returns null if up event is triggered
    val change: PointerInputChange? = awaitTouchSlopOrCancellation(down.id) { change, _ ->
        if (change.positionChange() != Offset.Zero) change.consume()
    }

    if (change != null) {
        drag(change.id) {
            pointer = it
            onDrag(pointer)
        }

        onDragEnd(pointer)
    } else {
        onDragEnd(pointer)
    }
}

fun Modifier.dragMotionEvent(
    onDragStart: (PointerInputChange) -> Unit = {},
    onDrag: (PointerInputChange) -> Unit = {},
    onDragEnd: (PointerInputChange) -> Unit = {}
) = this.then(
    Modifier.pointerInput(Unit) {
        awaitEachGesture {
            awaitDragMotionEvent(onDragStart, onDrag, onDragEnd)
        }
    }
)

enum class MotionEvent {
    IDLE, DOWN, MOVE, UP
}
